<template>
  <div class="file-upload">
    <div class="file-upload-wrapper">
      <h4 class="title">大文件上传</h4>
      <div class="upload-button mt-15">
        <div class="custom-upload">
          <label class="upload-label flex-center" for="uploadFile">
            <PlusOutlined />
          </label>
          <input
            class="file-upload"
            type="file"
            id="uploadFile"
            @change="handleUpload"
          />
        </div>
      </div>
      <span class="tips mt-10">上传进度会显示在下方~</span>

      <div class="upload-wrapper">
        <div
          class="complete-file-info flex-column-center mt-15"
          v-if="currentFile"
        >
          <p class="name">{{ currentFile.name }}</p>
          <a-progress
            :percent="totalPercent"
            style="width: 200px; margin: 0 16px"
          />
          <a-button :disabled="btnDisabled" @click="goonUpload">{{
            paused ? "继续上传" : "暂停上传"
          }}</a-button>
          <a-button class="ml-10" danger @click="cancelUpload"
            >取消上传</a-button
          >
        </div>
        <div class="upload-item-list" style="height: 500px; overflow-y: auto">
          <div
            v-for="item of uploadList"
            :key="item.hash"
            class="flex-column-center upload-item"
          >
            <div class="name">{{ item.hash }}</div>
            <a-progress
              :percent="item.percent"
              style="width: 200px; margin: 0 16px"
            />
          </div>
        </div>
      </div>

      <a-button @click="addButton">增加</a-button>
      <a-button @click="deleteButton">删除</a-button>
      <a-button @click="updateButton">修改</a-button>
      <a-button @click="searchButton">查询</a-button>
      <a-button @click="clearButton">清空表</a-button>
    </div>
  </div>
</template>

<script setup>
import { computed, onMounted, ref } from "vue";
import { message } from "ant-design-vue";
import { PlusOutlined } from "@ant-design/icons-vue";
import SparkMD5 from "spark-md5";

import { cancelUploadFile, mergeUpload, validateUpload } from "@api/upload";
import request from "@/utils/request";
import UploadManager from "@utils/uploadDB";
import BaseDB from "@utils/baseDB";
import { getLocal, removeLocal, setLocal } from "@utils/storage";

const upload = new UploadManager();
// 10MB
const SIZE = 500 * 1024;

const chunkList = ref([]);
const uploadList = ref([]);
const currentFile = ref(null);

const totalPercent = computed(() => {
  if (uploadList.value.length === 0) return 0;
  return calculatePercent();
});


let hasUploadList = ref([]);
function calculatePercent() {
  let uploadBytes = uploadList.value.reduce((total, item) => {
    // item.percent需要除以100
    const percent = Math.min(Math.max(item.percent / 100 || 0, 0), 1);
    return total + percent * item.chunk.size;
  }, 0);
  let hasUploadBytes =
    hasUploadList.value.length * uploadList.value[0].chunk.size;
  return Math.min(
    (((uploadBytes + hasUploadBytes) / currentFile.value.size) * 100).toFixed(
      2
    ),
    100
  );
}
// 1kb = 1024字节
// 单文件上传
const currentHash = ref(null);
async function handleUpload(e) {
  btnDisabled.value = false;
  paused.value = false;
  currentFile.value = e.target.files[0];
  calculateHash(currentFile.value).then(async (res) => {
    setLocal(
      "file",
      JSON.stringify({
        name: currentFile.value.name,
        hash: res,
        status: "uploading",
      })
    );
    validateUpload({
      fileName: currentFile.value.name,
      fileHash: res,
    }).then(async (valid) => {
      if (valid.tag) {
        currentHash.value = res;
        chunkList.value = createFileChunk(currentFile.value);
        uploadList.value = chunkList.value.map(({ file }, index) => {
          return {
            chunk: file,
            hash: currentHash.value + "_" + index,
            cIndex: index,
          };
        });
        await upload.addChunks(currentHash.value, uploadList.value);
        console.log(uploadList.value, "==44");
        await uploadChunks();
        // 已上传部分分片的情况
      } else if (valid.data && valid.data.length !== 0) {
        hasUploadList.value = valid.data.map((item) => ({
          cIndex: item,
        }));
        currentHash.value = res;
        chunkList.value = createFileChunk(currentFile.value).filter(
          (item, index) => {
            return !valid.data.includes(index);
          }
        );
        uploadList.value = chunkList.value.map(({ file }, index) => {
          return {
            chunk: file,
            hash: currentHash.value + "_" + index,
            cIndex: index,
          };
        });
        console.log(uploadList.value, "==44");
        await uploadChunks();
      } else {
        currentFile.value = null;
        removeLocal("file");
        message.success(valid.message);
      }
    });
  });
}

// 生成文件切片
function createFileChunk(file, size = SIZE) {
  const fileChunkList = [];
  let cur = 0;
  while (cur < file.size) {
    fileChunkList.push({ file: file.slice(cur, cur + size) });
    cur += size;
  }
  return fileChunkList;
}

// 生成文件hash
async function calculateHash(file, chunkSize = SIZE) {
  return new Promise((resolve) => {
    const spark = new SparkMD5.ArrayBuffer();
    const chunks = Math.ceil(file.size / chunkSize);
    let currentChunk = 0;

    const loadNext = () => {
      const start = currentChunk * chunkSize;
      const end = Math.min(start + chunkSize, file.size);
      const reader = new FileReader();

      reader.onload = (e) => {
        spark.append(e.target.result);
        currentChunk++;

        if (currentChunk < chunks) {
          loadNext();
        } else {
          resolve(spark.end());
        }
      };

      reader.readAsArrayBuffer(file.slice(start, end));
    };

    loadNext();
  });
}

const cancelControllers = new Map();
// 上传切片
async function uploadChunks() {
  const requestList = uploadList.value
    .map(({ chunk, hash, cIndex }) => {
      const formData = new FormData();
      formData.append("chunk", chunk);
      formData.append("hash", hash);
      formData.append("fileName", currentFile.value?.name || "");
      return {
        formData,
        cIndex,
      };
    })
    .map(async ({ formData, cIndex }, index) => {
      // 为每个请求创建AbortController
      const controller = new AbortController();
      cancelControllers.set(index, controller);
      await upload.updateChunkStatus(currentHash.value, index, "uploading");
      return request({
        url: "/large/file/upload",
        headers: { "Content-Type": "multipart/form-data" },
        method: "post",
        data: formData,
        signal: controller.signal,
        onUploadProgress: function (progressEvent) {
          uploadList.value[index].percent = Number(
            ((progressEvent.loaded / progressEvent.total) * 100).toFixed(2)
          );
        },
      }).then(async (res) => {
        if (res.code === 0) {
          await upload.updateChunkStatus(
            currentHash.value,
            cIndex,
            "completed"
          );
          await upload.cleanCompleted(currentHash.value);
        }
      });
    });
  console.log(requestList, "====220");
  Promise.all(requestList).then(async () => {
    console.log(11, "===22");
    mergeRequest();
  });
}

function clearUploadInfo() {
  removeLocal("file");
  currentFile.value = null;
  uploadList.value = [];
  upload.clearStore();
  hasUploadList.value = [];
}

function mergeRequest() {
  mergeUpload({
    fileName: currentFile.value.name,
    fileHash: currentHash.value,
  }).then((res) => {
    if (res.code === 0) {
      message.success("上传成功!");
      clearUploadInfo();
    }
  });
}

const testDB = ref(null);
onMounted(async () => {
  restoreUploads();
  const DB_NAME = "MyDataBase";
  const STORE_NAME = "myStore";
  testDB.value = new BaseDB(DB_NAME, STORE_NAME);
});
const btnDisabled = ref(true);
const paused = ref(false);
function goonUpload() {
  console.log(cancelControllers, "=====255");
  if (!paused.value) {
    cancelControllers.forEach((controller) => {
      let res = controller.abort();
      console.log(res, " ===28");
      console.log(controller, "===258");
    });
    cancelControllers.clear();
  } else {
    validateUpload({
      fileName: currentFile.value.name,
      fileHash: currentHash.value,
    }).then(async (res) => {
      if (res.tag) {
        await uploadChunks();
      } else if (res.data && res.data.length !== 0) {
        hasUploadList.value = res.data.map((item) => ({
          cIndex: item,
        }));
        uploadList.value = uploadList.value.filter((item, index) => {
          return !res.data.includes(index + "");
        });
        await uploadChunks();
      }
    });
  }
  paused.value = !paused.value;
  console.log(cancelControllers, "===280");
}
function cancelUpload() {
  cancelControllers.forEach((controller) => controller.abort());
  cancelControllers.clear();
  cancelUploadFile({
    fileHash: currentHash.value,
  }).then((res) => {
    if (res.code === 0) {
      clearUploadInfo();
    }
  });
}
// 若有因意外情况关闭的 判断是否继续上传
async function restoreUploads() {
  if (getLocal("file")) {
    let fileData = JSON.parse(getLocal("file"));
    currentHash.value = fileData.hash;
    currentFile.value = {
      name: fileData.name,
    };
    let res = await upload.getPendingChunks(currentHash.value);
    if (res.length !== 0) {
      console.log(res, "--===187");
      message.success("将继续恢复刚才中断的文件!");
      uploadList.value = res.map((item) => ({
        chunk: item.chunkData,
        hash: item.fileId + "_" + item.index,
        cIndex: item.index,
      }));
      uploadChunks();
    }
  }
}

function addButton() {
  const newItem = { emailIndex: "里四2", age: 28, fileId: 1 };
  testDB.value.addData(newItem).then(console.log);
}

function searchButton() {
  testDB.value.getAllData().then(console.log);
}

function updateButton() {
  const newItem = { emailIndex: "里四2", age: 32, fileId: 1 };
  testDB.value.updateData(newItem).then(console.log);
}

function deleteButton() {
  testDB.value.deleteData(9).then(console.log);
}

function clearButton() {
  testDB.value.clearStore().then(console.log);
}
</script>

<style scoped lang="less">
.file-upload {
  height: 100%;
  font-size: 16px;

  &-wrapper {
    height: 100%;
    padding: 16px 32px 0;

    .title {
      text-align: center;
      font-size: 20px;
      font-weight: bold;
    }

    .tips {
      font-size: 12px;
      display: inline-block;
    }
  }
}

.custom-upload {
  .upload-label {
    color: #333;
    border: 1px dashed #999;
    width: 120px;
    height: 120px;
    font-size: 36px;

    &:hover {
      cursor: pointer;
      opacity: 0.7;
    }
  }

  .file-upload {
    font-size: 0; // 隐藏按钮的文字
    height: 24px;
    width: 100%;
    display: none;

    &::file-selector-button {
      height: 24px;
      font-size: 14px;
      color: #333;
      text-align: left;
      font-size: 14px;
      background-color: #fff;
      border: none;
      width: 100%;
    }
  }
}

.upload-wrapper {
  font-size: 16px;
}
</style>
